Sommaire
De nos jours, il est devenu habituel d’avoir ce que l’on appelle un hôte bastion. Ce serveur est généralement dans un sous-réseau public et sert de passerelle SSH vers un sous-réseau privé. Les hôtes bastions sont essentiellement utilisés pour séparer les activités privées d’une attaque venant de l’extérieur, tout en fournissant un service accessible depuis l’extérieur. En l’occurence, un service SSH est souvent proposé à des prestataires extérieurs pour leur donner la possibilité d’interagir avec une machine avec l’obtention un pseudo-terminal.
Il est parfois nécessaire de passer successivement par plusieurs hôtes bastions pour d’atteindre le serveur avec lequel on souhaite interagir.
Ainsi, en se connectant en SSH successivement à chaque serveur intermédiaire il est possible d’atteindre le serveur cible. Cependant, il est possible d’automatiser l’ensemble des connexions intermédiaires. Pour ce faire, il existe plusieurs méthodes, soit on utilise la fonctionnalité ProxyCommand, qui offre beaucoup de souplesse en contre partie d’une certaine complexité, soit la fonctionnalité plus récente ProxyJump qui est bien plus simple à appréhender.
Dans tous les exemples qui suivront, nous nous placerons en tant que l’utilisateur Bob, avec la configuration de départ suivante :
bob:~$ ls -l ~/.ssh
total 16
-rw------- 1 demo demo 387 May 1 11:07 id_ed25519
-rw-r--r-- 1 demo demo 85 May 1 11:07 id_ed25519.pub
bob:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcqaPvZXFHgHzqOplzzmRg4AnxUYpFO0r4LrtFGsC/i bob
Pour illustrer nos propros, nous prendrons également l’architecture suivante comme exemple.
Configuration du serveur bastion A :
bob:~$ ssh admin@server_A
The authenticity of host 'server_a (140.82.53.221)' can't be established.
ED25519 key fingerprint is SHA256:FRO3JsAygUYpREAWWJR75wi6VLqIXdHi2uohJ3Fc0k4.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'server_a,140.82.53.221' (ED25519) to the list of known hosts.
Last login: Sat May 1 09:29:36 2021 from 176.160.93.220
admin@a:~$
admin@a:~$ ip a | grep "inet "
inet 127.0.0.1/8 scope host lo
inet 140.82.53.221/23 brd 140.82.53.255 scope global dynamic ens0
inet 10.0.0.1/24 scope global ens1
admin@a:~$ ls -l ~/.ssh/
total 12
-rw-r--r-- 1 admin admin 85 May 1 09:28 authorized_keys
-rw------- 1 admin admin 399 May 1 09:33 id_ed25519
-rw-r--r-- 1 admin admin 90 May 1 09:33 id_ed25519.pub
admin@a:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJAKbCinjGw/0BKTZnGP182A2XdvYD5mfTOz8TB06g23 server_a
admin@a:~$ cat ~/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcqaPvZXFHgHzqOplzzmRg4AnxUYpFO0r4LrtFGsC/i bob
Configuration du serveur B :
admin@a:~$ ssh admin@server_B
The authenticity of host 'server_b (10.0.0.2)' can't be established.
ED25519 key fingerprint is SHA256:KmxCNSOZGgulC9c4BfCwY4Sj3+n3Oe56hvr4Uvlxyv4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'server_b,10.0.0.2' (ED25519) to the list of known hosts.
Last login: Sat May 1 08:56:36 2021 from 176.160.93.220
admin@b:~$
admin@b:~$ ip a | grep "inet "
inet 127.0.0.1/8 scope host lo
inet 10.0.0.2/24 scope global ens0
admin@b:~$ ls -l ~/.ssh/
total 12
-rw-r--r-- 1 admin admin 90 May 1 09:34 authorized_keys
-rw------- 1 admin admin 399 May 1 09:34 id_ed25519
-rw-r--r-- 1 admin admin 90 May 1 09:34 id_ed25519.pub
admin@b:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOqDONyCJcsoBiKz7r53dvQMgjGaS/OELNq/gfAUDGx4 server_b
admin@b:~$ cat ~/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJAKbCinjGw/0BKTZnGP182A2XdvYD5mfTOz8TB06g23 server_a
Au lieu de se connecter d’abord en SSH à l’hôte du bastion A, puis d’utiliser à nouveau OpenSSH sur le bastion pour se connecter à l’hôte distant B, OpenSSH peut, depuis la version 7.3, créer lui-même les première et deuxième connexions en utilisant ProxyJump. On peut définir des noms d’utilisateur et des ports spécifiques s’ils diffèrent entre les hôtes :
$ ssh -J user@<bastion:port> <user@remote:port>
Cependant, il est important de noter que l’usage exclusif de l’authentification par clés cryptographiques requiert que notre clé publique soit positionné à la fois sur le serveur bastion A et sur le serveur de destination B. Actuellement, ce n’est pas le cas, aussi, nous faisont face à ce message d’erreur :
bob:~$ ssh -J admin@server_A admin@server_B
admin@server_b: Permission denied (publickey,keyboard-interactive).
Ce qui signifie que la premier tunnel SSH a bien été établi, mais que le second échoue à l’authentification. Il est donc primordial de pousser notre clé publique SSH sur l’ensemble des serveurs intermédiaires et sur le serveur de destination si l’on souhaite pouvoir utiliser convenablement la fonctionnalité ProxyJump. Nous verrons plus loin, qu’avec la méthode ProxyCommand, il est possible de récupérer successivement les identités des serveurs intermédiaires afin de ne pas être contraint de pousser notre clé publique SSH sur chaque serveurs.
Nous poussons donc notre clé publique SSH sur le serveur destination B.
bob:~$ cat ~/.ssh/id_ed25519.pub | ssh admin@server_A "cat | ssh admin@server_B \"cat >> .ssh/authorized_keys\""
Nous pouvons à présent nous connecter directement sur le serveur B.
bob:~$ ssh -J admin@server_A admin@server_B
Last login: Sat May 1 09:36:22 2021 from 10.0.0.1
admin@b:~$
admin@b:~$ cat ~/.ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJAKbCinjGw/0BKTZnGP182A2XdvYD5mfTOz8TB06g23 server_a
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcqaPvZXFHgHzqOplzzmRg4AnxUYpFO0r4LrtFGsC/i bob
Cette fonctionnalité est très pratique, mais on peut aller encore plus loin en l’utilisant directement depuis le fichier de configuration de notre client OpenSSH.
bob:~$ cat << EOF > ~/.ssh/config
> Host server_A
> Hostname server_A
> User admin
> PubkeyAuthentication yes
> IdentityFile ~/.ssh/id_ed25519
>
> Host server_B
> Hostname server_B
> User admin
> IdentityFile ~/.ssh/id_ed25519
> ProxyJump server_A
> EOF
bob:~$ ssh server_B
Last login: Sat May 1 09:59:47 2021 from 10.0.0.1
admin@b:~$
Un avantage indéniable est de pouvoir ici spécifier des identités différentes selon le serveur intermédiaire sur lequel on souhaite rebondir.
ProxyJump est la manière simplifiée d’utiliser une fonctionnalité que le client OpenSSH possède depuis longtemps : ProxyCommand. Elle fonctionne en transférant les entrées standard (stdin) et les sorties standard (stdout) de la machine distante à travers les hôtes intermédiaires. Pour ce faire, on doit spécifier la commande qui sera exécuté sur le serveur intermédiaire.
$ ssh -o ProxyCommand="ssh -W %h:%p bastion" user@<remote:port>
Les arguments %h:%p de l’option -W ci-dessus spécifient la transmission des entrées et sorties standard vers l’hôte distant, identifié par %h et le port de l’hôte distant, identifié par %p.
Comme précédemment, il est nécessaire que nous poussions notre clé publique SSH sur le serveur destination B, pour que l’authentification sur le serveur B puisse s’effectuer correctement.
bob:~$ ssh -o ProxyCommand="ssh -W %h:%p admin@server_A" admin@server_B
admin@server_b: Permission denied (publickey,keyboard-interactive).
bob:~$ cat ~/.ssh/id_ed25519.pub | ssh admin@server_A "cat | ssh admin@server_B \"cat >> .ssh/authorized_keys\""
bob:~$ ssh -o ProxyCommand="ssh -W %h:%p admin@server_A" admin@server_B
Last login: Sat May 1 10:07:56 2021 from 10.0.0.1
admin@b:~$
De même que pour ProxyJump il est possible d’écrire un fichier de configuration pour notre client OpenSSH.
bob:~$ cat << EOF > ~/.ssh/config
Host server_A
Hostname server_A
User admin
PubkeyAuthentication yes
IdentityFile ~/.ssh/id_ed25519
Host server_B
Hostname server_B
User admin
IdentityFile ~/.ssh/id_ed25519
ProxyCommand ssh -W %h:%p server_A
EOF
bob:~$ ssh server_B
Last login: Sat May 1 13:46:28 2021 from 10.0.0.1
admin@b:~$
On peut là aussi spécifier des identités différentes selon le serveur intermédiaire sur lequel on souhaite rebondir. Très pratique.
Maintenant, voyons comment éviter d’avoir à pousser notre clé publique SSH sur l’ensemble des serveurs qui se trouvent après le premier serveur de rebond. L’idée, c’est de tirer partie des identités déjà présente sur chaque intermédiaire. Pour se faire, les seules conditions que nous devons remplir sont :
•.notre clé publique SSH doit être connu du premier serveur de rebond
•.chaque intermédiaire doit connaître au moins une identité d’un serveur qui le précède dans la chaîne de connexion
•.les serveurs pour lesquels on souhaite tirer partie de leur identité doivent l’option AllowAgentForwarding activée
•.l’agent OpenSSH soit lancé sur notre machine
On supprime donc notre configuration précédente, ainsi que notre clé publique SSH sur le serveur de destination B.
bob:~$ ssh server_B sed -i '/bob/d' .ssh/authorized_keys
bob:~$ rm ~/.ssh/config
Partant de la configuration initiale, nous savons que les deux premières conditions sont déjà remplies. Vérifions maintenant que le serveur OpenSSH du serveur A ait bien l’option AllowAgentForwarding activée.
bob:~$ ssh -tt admin@server_A "sudo sshd -T | grep allowagentforwarding"
[sudo] password for admin:
allowagentforwarding yes
Connection to server_a closed.
On lance notre agent OpenSSH.
bob:~$ eval `ssh-agent -s`
Agent pid 71349
bob:~$ ssh-add
Identity added: /home/demo/.ssh/id_ed25519 (bob)
bob:~$ ssh-add -l
256 SHA256:p/0ADeqgtj7KNIDKmLME0SPQYKC4cbYMVmBT+1deBAA bob (ED25519)
On peut maintenant se connecter directement au serveur B en profitant de l’identité du serveur A.
bob:~$ ssh -o ProxyCommand="ssh -o ForwardAgent=yes admin@server_A 'ssh-add && nc %h %p'" admin@server_B
Identity added: /home/admin/.ssh/id_ed25519 (server_a)
Last login: Sun May 2 19:09:40 2021 from 10.0.0.1
admin@b:~$
Cependant, comme on peut le voir, l’identité du serveur A a été ajouté à notre agent OpenSSH local. On peut facilement le vérifier.
bob:~$ ssh-add -l
256 SHA256:p/0ADeqgtj7KNIDKmLME0SPQYKC4cbYMVmBT+1deBAA bob (ED25519)
256 SHA256:0TX2zjs3UnwFyC1z0/syQ/qnq7Ora1rDVdpEqS0VFaY server_a (ED25519)
Aussi, il est primordial de comprendre ici que la clé privée SSH du serveur A, n’est pas redescendu jusque notre machine, seulement sa capacité d’authentification.
C’est quelque chose qui faut prendre en compte, car si il s’agit de la capacité d’authentification bastion d’un partenaire, il serait cohérent que celui-ci refuse que celle-ci sorte de son périmètre.
Il est néanmoins fortement déconseillé de faire usage de la fonctionnalité ForwardAgent en raison de tous les problèmes de compromision que cela peut induire si mal utilisée. Il est donc préférable de demander le positionnement de notre clé publique personnelle sur l’ensemble des serveurs sur lesquels nous avons le droit de nous authentifier, et d’utiliser la fonctionnalité ProxyJump.
Nous verrons dans un prochain article comment une mauvaise utilisation de la redirection de port avec OpenSSH peut entraîner la compromission de notre propre système d’information.